/**
*copyright(c)2017-2020厦门风领科技有限公司
* http://www.fenglinghudong.com/
*
*贝塞尔工具
*甘代诗
* 2021-12-21 14:26:12
*/
import { v2, v3, Vec2, Vec3, macro, tween, Node, TweenEasing, bezier } from "cc";
import { RandomUtil } from "./RandomUtil";

export default class BezierUtil {
    /**
     * 贝塞尔曲线
     * @param t 
     * @param stX 开始点x
     * @param stY 开始点y
     * @param kongzhiX 控制点x
     * @param kongzhiY 控制点y
     * @param endX 结束点x
     * @param endY 结束点y
     */
    public static bezier(t: number, stX: number = 0, stY: number = 0, kongzhiX: number, kongzhiY: number, endX: number, endY: number): any {
        let tem = 1 - t;
        let pos = v2();
        pos.x = tem * tem * stX + 2 * t * tem * kongzhiX + t * t * endX
        pos.y = tem * tem * stY + 2 * t * tem * kongzhiY + t * t * endY
        return pos;//返回坐标位置
    }

    /**
     * 二次贝塞尔曲线
     * @param t T值
     * @param p0 起始点
     * @param p1 控制点
     * @param p2 目标点
     * @returns 根据T值计算出来的贝赛尔曲线点
     */
    public static CalculateCubicBezierPoint(t: number, p0: Vec3, p1: Vec3, p2: Vec3): Vec3 {
        if (t > 1) {
            t = 1;
        } else if (t < 0) {
            t = 0;
        }
        let u = 1 - t;
        let tt = t * t;
        let uu = u * u;

        let p: Vec3 = p0.clone().multiplyScalar(uu);
        p = p.add(p1.clone().multiplyScalar(2 * u * t));
        p = p.add(p2.clone().multiplyScalar(tt));
        return p;
    }

    /**
     * 二次贝塞尔曲线 集合点
     * @param startPoint 起始点
     * @param controlPoint 控制点
     * @param endPoint 目标点
     * @param segmentNum 点数量点
     * @returns 
     */
    public static GetBeizerList(startPoint: Vec3, controlPoint: Vec3, endPoint: Vec3, segmentNum: number): Vec3[] {
        let path: Vec3[] = []
        for (let i = 1; i <= segmentNum; i++) {
            let t = i / segmentNum;
            let pixel: Vec3 = BezierUtil.CalculateCubicBezierPoint(t, startPoint, controlPoint, endPoint);
            path.push(pixel);
        }
        return path;
    }

    /**
         * 三次贝塞尔
         * @param t 
         * @param posStart 
         * @param posCon1 
         * @param posCon2 
         * @param posEnd 
         * @returns 
         */
    public static bezierThree(t: number, posStart: Vec3, posCon1: Vec3, posCon2: Vec3, posEnd: Vec3): Vec3 {
        if (t > 1) {
            t = 1;
        }
        else if (t < 0) {
            t = 0
        }

        var n = (1 - t);
        var nn = n * n;
        var nnn = nn * n;
        var tt = t * t;
        var ttt = tt * t;
        var pStart: Vec3 = posStart.clone();
        var pos = posStart.clone();
        var pCon1: Vec3 = posCon1.clone();
        var pCon2: Vec3 = posCon2.clone();
        var pEnd: Vec3 = posEnd.clone();
        pos.add(pStart.multiplyScalar(nnn));
        pos.add(pCon1.multiplyScalar(3 * nn * t));
        pos.add(pCon2.multiplyScalar(3 * n * tt));
        pos.add(pEnd.multiplyScalar(ttt));
        return pos;
    }

    public static Beziers(posArr: Vec3[], t: number) {
        let localPos = v3();
        let n = posArr.length - 1;
        for (let i = 0; i <= n; i++) {
            let item: Vec3 = posArr[i];
            let index = i;
            if (index == 0) {
                localPos = localPos.add(item.clone().multiplyScalar(Math.pow((1 - t), n - index)).multiplyScalar(Math.pow(t, index)));
            }
            else {
                let m1: number = BezierUtil.fact(n);
                let m2: number = BezierUtil.fact(index);
                let m3: number = BezierUtil.fact(n - index);
                let m4: number = Math.pow((1 - t), n - index);
                let m5: number = Math.pow(t, index);
                localPos = localPos.add(item.clone().multiplyScalar(m1 / m2 / m3 * m4 * m5));
                // localPos += m1 / m2 / m3 * item * m4 * m5;
            }
        }
        return localPos;
    }

    public static fact(n: number): number {
        if (n == 0) {
            return 1.0;
        }
        else {
            let f = 1;
            for (let i = 1; i <= n; i++) {
                f *= i;
            }
            return f;
        }
    }

    /**
       * 贝塞尔计算公式
       * @param t 
       * @param bezierList 当前计算数组
       * @returns 计算后数值
       */
    public static getBezier(t: number, bezierList: Array<number>) {
        if (bezierList.length === 2) {//直路
            return (1 - t) * bezierList[0] + t * bezierList[1];
        } else if (bezierList.length === 3) {//弯路
            return (1 - t) * (1 - t) * bezierList[0] + 2 * t * (1 - t) * bezierList[1] + t * t * bezierList[2];
        } else {
            //其余暂时用不到 不处理
            console.error('未补充' + bezierList.length + '次方公式');
            return 0;
        }
    }
    /**
     * 计算当前存储的配置计算出贝塞尔曲线
     * @param mapBezierData 
     * @param initOffset 
     * @param cb 
     */
    public static getBezierCalculateList(mapBezierData: any, initOffset: number, aidata: any, cb: any, dis = 0.11) {
        let bezierList = []; //当前经过贝塞尔计算之后的所有坐标
        let t = 0;
        let nowData;
        let nowPos: { x: number, z: number } = null!;
        let nextData;
        let nextPos: { x: number, z: number } = { x: 0, z: 0 };
        let xList = [] as Array<number>;
        let zList = [] as Array<number>;
        let nowId = 1;

        const pushPosToList = (pos: { x: number, z: number }) => {
            xList.push(pos.x);
            zList.push(pos.z);
        }

        let offset = initOffset;
        if (aidata) {
            nowId = 0; //如果存在角色坐标 就从0开始
            mapBezierData[0] = aidata;
        }

        while (nowId !== 999) { //id===999 当前路径结束
            xList.length = 0;
            zList.length = 0;
            nowData = mapBezierData[nowId];
            let nextIdListNum = 0;
            if (nowData.nextIdList.length > 1) {
                nextIdListNum = Math.floor(Math.random() * nowData.nextIdList.length)
            }
            nextData = mapBezierData[nowData.nextIdList[nextIdListNum]];

            if (!nowPos) { //不存在 当前坐标 初始化
                if (nowId === 0) {
                    nowPos = { x: nowData.pos.x, z: nowData.pos.z };
                    const addX = offset * Math.sin((nextData.eulY + 90) * macro.RAD);
                    const addZ = offset * Math.cos((nextData.eulY + 90) * macro.RAD);
                    nextPos = { x: nextData.pos.x + addX, z: nextData.pos.z + addZ };
                } else {
                    const addX = offset * Math.sin((nowData.eulY + 90) * macro.RAD);
                    const addZ = offset * Math.cos((nowData.eulY + 90) * macro.RAD);
                    nowPos = { x: nowData.pos.x + addX, z: nowData.pos.z + addZ }
                    nextPos = { x: nextData.pos.x + addX, z: nextData.pos.z + addZ };
                }

                pushPosToList(nowPos);
            } else {
                pushPosToList(nowPos);

                if (nextData.isBendEnd) { //当前是弯道的情况 重置偏移量 并计算偏移下一次的坐标
                    offset = RandomUtil.getRandomRange(0.4, true);
                    const addX = offset * Math.sin((nextData.eulY + 90) * macro.RAD);
                    const addZ = offset * Math.cos((nextData.eulY + 90) * macro.RAD);
                    nextPos = { x: nextData.pos.x + addX, z: nextData.pos.z + addZ };
                    //当前是弯道 算出来的当前三个点确定的曲线贝塞尔
                    if (nowData.eulY - nextData.eulY < 0) {
                        pushPosToList({ x: nowPos.x, z: nextPos.z });
                    } else {
                        pushPosToList({ x: nextPos.x, z: nowPos.z });
                    }
                } else {
                    nextPos = nextData.pos;
                    let nowInitPos = nowData.pos;
                    //直路 朝着正前方行走
                    const rad = this.checkTwoPosEulRad(nowInitPos.x, nowInitPos.z, nextPos.x, nextPos.z);
                    const posLength = Math.sqrt((nowInitPos.x - nextPos.x) * (nowInitPos.x - nextPos.x)
                        + (nowInitPos.z - nextPos.z) * (nowInitPos.z - nextPos.z));
                    nextPos = { x: nowPos.x - Math.sin(rad) * posLength, z: nowPos.z - Math.cos(rad) * posLength };
                }
            }
            pushPosToList(nextPos);

            let subPos = this.getTwoPosXZLength(nowPos.x, nowPos.z, nextPos.x, nextPos.z);
            let countNum = Math.round(subPos / dis);

            for (let k = 0; k < countNum; k++) {
                let pos = new Vec2(0, 0); //x值对应pos.x z值对应pos.y 节省多余的y的数据 
                t = k / countNum;

                pos.x = Number((this.getBezier(t, xList).toFixed(3)));
                pos.y = Number((this.getBezier(t, zList).toFixed(3)));

                bezierList.push(pos);
            }
            //当前id 设置为下一次的id————id衔接
            nowId = nowData.nextIdList[nextIdListNum];
            //当前坐标 设置为下一次的坐标————坐标衔接
            nowPos = nextPos;

            if (nowId === 999) {//结束的时候 将最后一个结束点放入数组
                bezierList.push(new Vec2(Number((nowPos.x.toFixed(3))), Number((nowPos.z.toFixed(3)))));
                cb(bezierList);
            }
        }
    }
    /**
    * 计算两个节点的xz坐标的弧度
    */
    public static checkTwoPosEulRad(x1: number, z1: number, x2: number, z2: number) {
        return Math.atan2(x1 - x2, z1 - z2);
    }
    /**获取两个坐标在xz轴的距离 */
    public static getTwoPosXZLength(x1: number, z1: number, x2: number, z2: number) {
        const x = x1 - x2;
        const z = z1 - z2;
        return Math.sqrt(x * x + z * z);
    }

    /**
    *  二阶贝塞尔曲线 运动
    * @param target
    * @param {number} duration
    * @param {} c1 起点坐标
    * @param {} c2 控制点
    * @param {Vec3} to 终点坐标
    * @param opts
    * @returns {any}
    */
    public static bezierTo(target: Node, duration: number, c1: Vec3, c2: Vec3, to: Vec3, call?: Function, opts?: any) {
        opts = opts || Object.create(null);
        let twoBezier = (t: number, p1: Vec3, cp: Vec3, p2: Vec3) => {
            let x = (1 - t) * (1 - t) * p1.x + 2 * t * (1 - t) * cp.x + t * t * p2.x;
            let y = (1 - t) * (1 - t) * p1.y + 2 * t * (1 - t) * cp.y + t * t * p2.y;
            let z = (1 - t) * (1 - t) * p1.z + 2 * t * (1 - t) * cp.z + t * t * p2.z;
            return v3(x, y, z);
        };
        opts.onUpdate = (arg: Vec3, ratio: number) => {
            target.position = twoBezier(ratio, c1, c2, to);
        };
        return tween(target).to(duration, {}, opts)
            .call(call)
            .start();
    }

    /**
     *  二阶贝塞尔曲线 运动
     * @param target 目标节点
     * @param {number} time 总运行时间
     * @param {Vec3} finalPos 终点坐标
     * @param {Vec3} controllPos 控制点
     * @param {Function} callBack 回调
     */
    public static playBezierTween(target: Node, time: number, finalPos: Vec3, controllPos: Vec3, tweenType?: TweenEasing, callBack?) {
        let startPos = target.position;
        let targetTween = tween(startPos);
        const mixY = controllPos.y;
        const maxY = controllPos.y;
        const mixX = controllPos.x;
        const maxX = controllPos.x;
        const mixZ = controllPos.z;
        const maxZ = controllPos.z;
        let progressX = function (start, end, current, t) {
            //@ts-ignore
            current = bezier(start, mixX, maxX, end, t);
            return current;
        };
        let progressY = function (start, end, current, t) {
            //@ts-ignore
            current = bezier(start, mixY, maxY, end, t);
            return current;
        };

        let progressZ = function (start, end, current, t) {
            //@ts-ignore
            current = bezier(start, mixZ, maxZ, end, t);
            return current;
        };

        let easingType: TweenEasing = tweenType ? tweenType : 'smooth';

        return targetTween.parallel(
            tween().to(time, { x: finalPos.x }, {
                progress: progressX, easing: easingType, onUpdate: () => {
                    target.setPosition(startPos);
                }
            }),
            tween().to(time, { y: finalPos.y }, {
                progress: progressY, easing: easingType, onUpdate: () => {
                    target.setPosition(startPos);
                }
            }),
            tween().to(time, { z: finalPos.z }, {
                progress: progressZ, easing: easingType, onUpdate: () => {
                    target.setPosition(startPos);
                }
            }),
        )
            .call(() => {
                callBack && callBack();
            })
            .start();
    }

    /**
     * 二阶贝塞尔曲线运动 扩展
     * @param target
     * @param {number} delayTime 延迟时间
     * @param {number} time 总动画时间
     * @param {Vec3} finalPos 终点坐标
     * @param {Vec3} controllPos 控制点
     * @param {Function} callBack 回调
     */
    public static playBezierTweenEx(target: Node, time: number, delayTime: number, finalPos: Vec3, controllPos: Vec3, tweenType?: TweenEasing, callBack?: Function) {
        let startPos = target.position;
        let targetTween = tween(startPos);
        const mixY = controllPos.y;
        const maxY = controllPos.y;
        const mixX = controllPos.x;
        const maxX = controllPos.x;
        const mixZ = controllPos.z;
        const maxZ = controllPos.z;
        let progressX = function (start, end, current, t) {
            //@ts-ignore
            current = bezier(start, mixX, maxX, end, t);
            return current;
        };
        let progressY = function (start, end, current, t) {
            //@ts-ignore
            current = bezier(start, mixY, maxY, end, t);
            return current;
        };

        let progressZ = function (start, end, current, t) {
            //@ts-ignore
            current = bezier(start, mixZ, maxZ, end, t);
            return current;
        };

        let easingType: TweenEasing = tweenType ? tweenType : 'smooth';

        return targetTween
            .delay(delayTime)
            .parallel(
                tween().to(time, { x: finalPos.x }, {
                    progress: progressX, easing: easingType, onUpdate: () => {
                        target.setPosition(startPos);
                    }
                }),
                tween().to(time, { y: finalPos.y }, {
                    progress: progressY, easing: easingType, onUpdate: () => {
                        target.setPosition(startPos);
                    }
                }),
                tween().to(time, { z: finalPos.z }, {
                    progress: progressZ, easing: easingType, onUpdate: () => {
                        target.setPosition(startPos);
                    }
                }),
            )
            .call(() => {
                callBack && callBack();
            })
            .start();
    }
}